﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Security.Permissions;
using YiNing.Tools;

namespace IPluginInterface.Loader
{

	/// <summary>
	///	当托管在单独的应用程序域中时，提供加载机制
	/// 插件程序集并实例化其中的对象。
	/// </summary>
	[SecurityPermission(SecurityAction.Demand, Infrastructure = true)]
	internal sealed class PluginLoader : MarshalByRefObject, IDisposable {


		/// <summary>
		/// 获取或设置包含程序集的目录。
		/// </summary>
		private string PluginDir { get; set; }
		/// <summary>
		/// 获取或设置已加载的程序集的集合。
		/// </summary>
		private List<Assembly> Assemblies { get; set; }
		/// <summary>
		/// 获取或设置各种接口类型的构造函数集合。
		/// </summary>
		private Dictionary<Type, LinkedList<ConstructorInfo>> ConstructorCache { get; set; }

		/// <summary>
		/// 初始化 PluginLoader 类的新实例。
		/// </summary>
		public PluginLoader() {
			ConstructorCache = new Dictionary<Type, LinkedList<ConstructorInfo>>();
			Assemblies = new List<Assembly>();
		}

		~PluginLoader() {
			Dispose(false);
		}

		/// <summary>
		/// 将插件程序集加载到应用程序域中并填充插件集合。
		/// </summary>
		/// <param name="pluginDir"></param>
		/// <param name="disabledPlugins"></param>
		public void Init(string pluginDir, string fileName = "*.dll") {
			Uninit();

			PluginDir = pluginDir;

			foreach (string dllFile in Directory.GetFiles(PluginDir, fileName)) {
				try {
					Assembly asm = Assembly.LoadFile(dllFile);
					LogHelper.WriteLog(string.Format("Loaded assembly {0}.", asm.GetName().Name));
					// TODO：限制基于数字签名加载的程序集，
					// 实现所需的接口、DRM 等

					Assemblies.Add(asm);
				}
				catch (ReflectionTypeLoadException rex) {
					LogHelper.WriteLog(string.Format("Plugin {0} failed to load.", Path.GetFileName(dllFile)));
					foreach (Exception ex in rex.LoaderExceptions) {
						LogHelper.WriteLog(string.Format("\t{0}: {1}", ex.GetType().Name, ex.Message));
					}
				}
				catch (BadImageFormatException) {
					// 忽略，这仅表示 DLL 不是 .NET 程序集
					LogHelper.WriteLog(string.Format("Plugin {0} is not a valid assembly.", Path.GetFileName(dllFile)));
				}
				catch (Exception ex) {
					LogHelper.WriteLog(string.Format("Plugin {0} failed to load.", Path.GetFileName(dllFile)));
					LogHelper.WriteLog(string.Format("\t{0}: {1}", ex.GetType().Name, ex.Message));
				}
			}
		}

		/// <summary>
		/// 清除所有插件程序集和类型信息。
		/// </summary>
		public void Uninit() {
			Assemblies.Clear();
			ConstructorCache.Clear();
		}

		/// <summary>
		/// 返回实现
		/// 特定界面。任何属于 MarshalByRefObject 的实例
		/// 必须防止断开连接。
		/// </summary>
		/// <typeparam name="TInterface"></typeparam>
		/// <returns></returns>
		public IEnumerable<KeyValuePair<string, TInterface>> GetImplementations<TInterface>() {
			LinkedList<KeyValuePair<string, TInterface>> instances = new LinkedList<KeyValuePair<string, TInterface>>();

			foreach (ConstructorInfo constructor in GetConstructors<TInterface>()) {
				instances.AddLast(new KeyValuePair<string, TInterface>(constructor.DeclaringType.Assembly.Location, CreateInstance<TInterface>(constructor)));
			}
			return instances;
		}

		public AssemblyName GetOwningAssembly(object instance) {
			Type type = instance.GetType();
			return type.Assembly.GetName();
		}

		public string GetTypeName(object instance) {
			Type type = instance.GetType();
			return type.FullName;
		}

		public KeyValuePair<string, TInterface> GetImplementation<TInterface>() {
			return GetImplementations<TInterface>().FirstOrDefault();
		}

		private IEnumerable<ConstructorInfo> GetConstructors<TInterface>() {
			if (ConstructorCache.ContainsKey(typeof(TInterface))) {
				return ConstructorCache[typeof(TInterface)];
			}
			else {
				LinkedList<ConstructorInfo> constructors = new LinkedList<ConstructorInfo>();

				foreach (Assembly asm in Assemblies) {
					try
					{
						foreach (Type type in asm.GetTypes())
						{
							if (type.IsClass && !type.IsAbstract)
							{
								if (type.GetInterfaces().Contains(typeof(TInterface)))
								{
									ConstructorInfo constructor = type.GetConstructor(Type.EmptyTypes);
									constructors.AddLast(constructor);
								}
							}
						}
					}
					catch (Exception er)
					{

					}
		
				}

				ConstructorCache[typeof(TInterface)] = constructors;
				return constructors;
			}
		}

		private IEnumerable<TInterface> GetImplementations<TInterface>(Assembly assembly) {
			List<TInterface> instances = new List<TInterface>();

			foreach (Type type in assembly.GetTypes()) {
				if (type.IsClass && !type.IsAbstract) {
					if (type.GetInterfaces().Contains(typeof(TInterface))) {
						TInterface instance = default(TInterface);
						ConstructorInfo constructor = type.GetConstructor(Type.EmptyTypes);
						instance = CreateInstance<TInterface>(constructor);
						if (instance != null) instances.Add(instance);
					}
				}
			}

			return instances;
		}

		private TInterface CreateInstance<TInterface>(ConstructorInfo constructor) {
			TInterface instance = default(TInterface);

			try {
				instance = (TInterface)constructor.Invoke(null);
			}
			catch (Exception ex) {
				LogHelper.WriteLog(string.Format("Unable to instantiate type {0} in plugin {1}",
					constructor.ReflectedType.FullName,
					Path.GetFileName(constructor.ReflectedType.Assembly.Location)));
				LogHelper.WriteLog(string.Format("\t{0}: {1}", ex.GetType().Name, ex.Message));
			}

			return instance;
		}

		private TInterface GetImplementation<TInterface>(Assembly assembly) {
			return GetImplementations<TInterface>(assembly).FirstOrDefault();
		}

		#region IDisposable Members

		public void Dispose() {
			Dispose(true);
			GC.SuppressFinalize(this);
		}

		private void Dispose(bool disposing) {
			if (disposing) {
				Uninit();
			}
		}

		#endregion
	}
}